package es.uc3m.ce.practice1.ex4;

/**
 * Given two float numbers, multiply those numbers and return the result in IEEE 754 standard representation.
 * The multiplication is done in IEEE 754 standard representation
 * 
 * @author Adrián Gil Moral - 100293146
 * @author Rafael Medina García - 100292314
 */
public class RealMultiplier {

	/* <--- BEGIN FLOAT TO IEEE 754 */
	
	/**
	 * Convert the integer part of the number into binary form
	 * @param intNumber integer number to convert
	 * @return Binary representation of the integer part of the number
	 */
	private static String integerToBinary(int intNumber) {
		String binaryNumber = "";
		String result = "";
		// If the number is 0, return 0
		if (intNumber == 0) {
			return "0";
		}
		
		// Convert the number into binary
		while (intNumber >= 1) {
			binaryNumber += intNumber % 2;
			intNumber /= 2;
		}

		// Reverse the order of the digits so the binary number is written correctly
		for (int i = binaryNumber.length() - 1; i >= 0; i--) {
			result += binaryNumber.charAt(i);
		}

		// Return the number in binary
		return result;
	}
	
	/**
	 * Convert the irrational part of the number into binary form
	 * @param irrNumber irrational number to convert
	 * @return Binary representation of the irrational part of the number
	 */
	private static String irrationalToBinary(double irrNumber) {
		String result = "";
		int i = 0;
		
		// Convert to binary
		while (i < (24)) {
			irrNumber *= 2;
			// Bigger than 1
			if (irrNumber > 1) {
				result += "1";
				irrNumber -= 1;
				// Minor than 1
			} else if (irrNumber < 1) {
				result += "0";
				// Equal to 1
			} else {
				result += "1";
				break;
			}
			i++;
		}
		
		// Return the number in binary
		return result;
	}
	
	/**
	 * Convert a floating point number into its binary representation
	 * @param number number to convert
	 * @return Binary representation of the number
	 */
	private static String numToBinary(float number) {
		String result = "";
		
		// Convert the integer part into binary
		result += integerToBinary((int)Math.floor(number));
		
		// Add the separator
		if (number - Math.floor(number) != 0)
			result += ".";
		
		// Convert the irrational part into binary
		result += irrationalToBinary(number - Math.floor(number));
		
		// Return the number in binary
		return result;
		
	}
	
	/**
	 * Normalize the binary number
	 * @param binaryNumber number to normalize
	 * @return Normalization of the binary number
	 */
	private static String normalize(String binaryNumber) {
		String result = "";
		/* Move the decimal separator */
		int posDot = 0;
		int posOne = 0;
		// Find the '.'
		for (int i = 0; i < binaryNumber.length(); i++) {
			if (binaryNumber.charAt(i) == '.') {
				posDot = i;
				break;
			}
		}
		// Find the first 1
		for (int i = 0; i < binaryNumber.length(); i++) {
			if (binaryNumber.charAt(i) == '1') {
				posOne = i;
				break;
			}
		}
		// Move the separator
		// Write until the first 1
		for (int i = 0; i <= posOne; i++) {
			result += binaryNumber.charAt(i);
		}
		// Add the '.'
		result += ".";
		// Add the rest of the number
		for (int i = posOne + 1; i < posDot; i++) {
			result += String.valueOf(binaryNumber.charAt(i));
		}
		for (int i = posDot + 1; i < binaryNumber.length(); i++) {
			result += String.valueOf(binaryNumber.charAt(i));
		}

		return result;
		
	}
	
	/**
	 * Calculate the exponent of the IEEE 754 number
	 * @param oldDot the previous position of the decimal separator
	 * @param binaryNumber the binary number to calculate the exponent from
	 * @return Exponent of the IEEE 754 number for later transformation into binary
	 */
	private static int getExponent(int oldDot, String binaryNumber) {
		int newDot = 0;
		// Get the position of the separator
		for (int i = 0; i < binaryNumber.length(); i++) {
			if (binaryNumber.charAt(i) == '.') {
				newDot = i;
				break;
			}
		}
		// Get exponent
		return ((oldDot - newDot) + 127);
	}
	
	/**
	 * Round the significant part of the binary number.
	 * This code will retain the significant bit.
	 * @param binaryNumber the binary number to round
	 * @return Rounded significant part of the IEEE 754 number
	 */
	private static String roundSignificant(String binaryNumber) {
		// Exclude the decimal separator
		String[] temp = binaryNumber.split("[.]");
		// Get the relevant part
		String[] significant = new String[temp[1].length()];
		for (int i = 0; i < significant.length; i++) {
			significant[i] = String.valueOf(temp[1].charAt(i));
		}
		
		// Find the length of the number
		int length = significant.length;
		
		// Round the number
		if (length > 23) {
			int it = 23;
			boolean rounded = false;
			boolean carry = false;
			// Start rounding
			while (it > 1 && rounded == false) {
				// -- No carry --
				if (significant[it] == "1" && significant[it-1] == "0") {
					significant[it-1] = "1";
					it--;
					rounded = true;
				} else if (significant[it] == "1" && significant[it-1] == "1"){
					significant[it-1] = "0";
					it--;
					carry = true;
				// -- Carry --
				} else if (carry = true && significant[it-1] == "0") {
					significant[it-1] = "1";
					it--;
					carry = false;
					rounded = true;
				} else if (carry = true && significant[it-1] == "1") {
					significant[it-1] = "0";
					carry = true;
					it--;
				}
			}
		}
		
		// Return the number
		String result = temp[0] + ".";
		for (int i = 0; i < significant.length; i++) {
			result += significant[i];
		}
		return result;
	}
	
	/**
	 * Add 0 to the significant until there are 23 bits
	 * @param significant the significant to which add 0
	 * @return Final result for the significant
	 */
	private static String addZeros(String significant) {
		// Add 0s to the number until we reach 23 bits
		int bits = significant.length();
		for (int i = bits - 1; i < 24; i++) {
			significant += "0";
		}
		return significant;
	}
	
	/**
	 * Transform a floating point number into IEEE 754 standard representation
	 * @param number the number to transform
	 * @return IEEE 754 standard representation of the given number
	 */
	private static String floatToIEEE754(float number) {
		String ie3Result = "";
		
		/* Check if the number positive or negative */
		// Negative
		if (number < 0) {
			ie3Result += "1 ";
		} else {
		// Positive
			ie3Result += "0 ";
		}
		
		/* Convert the number into binary */
		String binaryNumber = "";
		// Get rid of the sign
		binaryNumber = numToBinary(Math.abs(number));
		// Find the separator to calculate the exponent
		int posDot = 0;
		for (int i = 0; i < binaryNumber.length(); i++) {
			if (binaryNumber.charAt(i) == '.') {
				posDot = i;
				break;
			}
		}
		// Normalize the number
		binaryNumber = normalize(binaryNumber);
		
		/* Calculate the exponent */
		int exponent = getExponent(posDot, binaryNumber);
		// Convert the exponent into binary
		if (exponent == 127) { // If the exponent is 127, we need to add a 0
			ie3Result += "0" + integerToBinary(exponent) + " ";
		} else {
		ie3Result += integerToBinary(exponent) + " ";
		}
		
		/* Round the significant */
		binaryNumber = roundSignificant(binaryNumber);
		// Add the significant part to the result
		ie3Result += addZeros(binaryNumber);
		
		// Return the IEEE 754 representation
		return ie3Result;
		
		
	}
	
	/* END FLOAT TO IEEE 754 ---> */
	
	/* <--- BEGIN MULTIPLICATION */
	
	/**
	 * Get the sign that will result from the multiplication
	 * @param ie3Num1 first IEEE 754 number
	 * @param ie3Num2 second IEEE 754 number
	 * @return Sign of the result of the multiplication in IEEE 754
	 */
	private static String getSign(String ie3Num1, String ie3Num2) {
		// Both positive
		if (ie3Num1.charAt(0) == '0' && ie3Num2.charAt(0) == '0') {
			return "0";
		// Both negative	
		} else if (ie3Num1.charAt(0) == '1' && ie3Num2.charAt(0) == '1') {
			return "0";
		}
		// One negative and one positive
		return "1";
	}
	
	/**
	 * Add two exponents
	 * @param ie3Num1 the first IEEE 754 number from which to get the exponent
	 * @param ie3Num2 the second IEEE 754 number from which to get the exponent
	 * @return Result of the addition of the exponents
	 */
	private static String addExponents(String ie3Num1, String ie3Num2) {
		String exp1 = "";
		String exp2 = "";
		
		/* Get the exponents */
		for (int i = 2; i < 10; i++) {
			exp1 += String.valueOf(ie3Num1.charAt(i));
			exp2 += String.valueOf(ie3Num2.charAt(i));
		}
		
		/* Add the exponents */
		boolean carry = false;
		String tempResult = "";
		String expResult = "";
		// Add from end to start
		for (int i = exp1.length() - 1; i >= 0; i--) {
			// NO CARRY
			if (!carry) {
				// Case 0 + 0
				if (exp1.charAt(i) == '0' && exp2.charAt(i) == '0') {
					tempResult += "0";
					carry = false;
				// Case 0 + 1	
				} else if (exp1.charAt(i) == '0' && exp2.charAt(i) == '1') {
					tempResult += "1";
					carry = false;
				// Case 1 + 0	
				} else if (exp1.charAt(i) == '1' && exp2.charAt(i) == '0') {
					tempResult += "1";
					carry = false;
				// Case 1 + 1	
				} else if (exp1.charAt(i) == '1' && exp2.charAt(i) == '1') {
					tempResult += "0";
					carry = true;
				}
			// CARRY	
			} else {
				// Case 0 + 0 + (1)
				if (exp1.charAt(i) == '0' && exp2.charAt(i) == '0') {
					tempResult += "1";
					carry = false;
				// Case 0 + 1 + (1)	
				} else if (exp1.charAt(i) == '0' && exp2.charAt(i) == '1') {
					tempResult += "0";
					carry = true;
				// Case 1 + 0 + (1)	
				} else if (exp1.charAt(i) == '1' && exp2.charAt(i) == '0') {
					tempResult += "0";
					carry = true;
				// Case 1 + 1 + (1)	
				} else if (exp1.charAt(i) == '1' && exp2.charAt(i) == '1') {
					tempResult += "1";
					carry = true;
				}
			}
		}
		// If there is carry, we have one extra bit
		if(carry) {
			tempResult += "1";
		}
		// Reverse the order of the digits so the binary number is written correctly
		for (int i = tempResult.length() - 1; i >= 0; i--) {
			expResult += tempResult.charAt(i);
		}
		
		// Return the result of the addition
		return expResult;
	}
	
	/**
	 * Add two binary numbers
	 * @param twoComp1 the first number to add
	 * @param twoComp2 the second number to add
	 * @return Result of the addition
	 */
	private static String addNumbers(String num1, String num2) {
		if (num2.length() < num1.length()) {
			for (int i = 0; i < (num1.length() - num2.length()); i++) {
				num2 = "0" + num2;
			}
		}
		if (num1.length() < num2.length()) {
			for (int i = 0; i < (num2.length() - num1.length()); i++) {
				num1 = "0" + num1;
			}
		}
		/* Add the numbers */
		boolean carry = false;
		String tempResult = "";
		String addResult = "";
		// Add from end to start
		for (int i = num2.length() - 1 ; i >= 0; i--) {
			// NO CARRY
			if (!carry) {
				// Case 0 + 0
				if (num1.charAt(i) == '0' && num2.charAt(i) == '0') {
					tempResult += "0";
					carry = false;
				// Case 0 + 1	
				} else if (num1.charAt(i) == '0' && num2.charAt(i) == '1') {
					tempResult += "1";
					carry = false;
				// Case 1 + 0	
				} else if (num1.charAt(i) == '1' && num2.charAt(i) == '0') {
					tempResult += "1";
					carry = false;
				// Case 1 + 1	
				} else if (num1.charAt(i) == '1' && num2.charAt(i) == '1') {
					tempResult += "0";
					carry = true;
				}
			// CARRY	
			} else {
				// Case 0 + 0 + (1)
				if (num1.charAt(i) == '0' && num2.charAt(i) == '0') {
					tempResult += "1";
					carry = false;
				// Case 0 + 1 + (1)	
				} else if (num1.charAt(i) == '0' && num2.charAt(i) == '1') {
					tempResult += "0";
					carry = true;
				// Case 1 + 0 + (1)	
				} else if (num1.charAt(i) == '1' && num2.charAt(i) == '0') {
					tempResult += "0";
					carry = true;
				// Case 1 + 1 + (1)	
				} else if (num1.charAt(i) == '1' && num2.charAt(i) == '1') {
					tempResult += "1";
					carry = true;
				}
			}
		}
		// There is one extra carry
		if (carry) addResult += "1";
		// Reverse the order of the digits so the binary number is written correctly
		for (int i = tempResult.length() - 1; i >= 0; i--) {
			addResult += tempResult.charAt(i);
		}
		
		// Return the result of the addition
		return addResult;
	}
	
	/**
	 * Substract the bias from the exponent
	 * @param exp1 the exponent from which to substract the bias
	 * @return Final exponent
	 */
	private static String substractBias(String exp1) {
		/* Add the numbers */
		boolean carry = false;
		String bias = integerToBinary(127);
		String tempResult = "";
		String addResult = "";
		// Add from end to start
		for (int i = bias.length() - 1 ; i >= 0; i--) {
			// NO CARRY
			if (!carry) {
				// Case 0 - 0
				if (exp1.charAt(i) == '0' && bias.charAt(i) == '0') {
					tempResult += "0";
					carry = false;
				// Case 0 - 1	
				} else if (exp1.charAt(i) == '0' && bias.charAt(i) == '1') {
					tempResult += "1";
					carry = true;
				// Case 1 - 0	
				} else if (exp1.charAt(i) == '1' && bias.charAt(i) == '0') {
					tempResult += "1";
					carry = false;
				// Case 1 - 1	
				} else if (exp1.charAt(i) == '1' && bias.charAt(i) == '1') {
					tempResult += "0";
					carry = false;
				}
			// CARRY	
			} else {
				// Case (1) + 0 - 0
				if (exp1.charAt(i) == '0' && bias.charAt(i) == '0') {
					tempResult += "1";
					carry = true;
				// Case (1) + 0 - 1	
				} else if (exp1.charAt(i) == '0' && bias.charAt(i) == '1') {
					tempResult += "0";
					carry = true;
				// Case (1) + 1 - 0
				} else if (exp1.charAt(i) == '1' && bias.charAt(i) == '0') {
					tempResult += "0";
					carry = false;
				// Case (1) + 1 - 1
				} else if (exp1.charAt(i) == '1' && bias.charAt(i) == '1') {
					tempResult += "1";
					carry = true;
				}
			}
		}
		
		// Reverse the order of the digits so the binary number is written correctly
		for (int i = tempResult.length() - 1; i >= 0; i--) {
			addResult += tempResult.charAt(i);
		}
		
		// Add 0
		for (int i = tempResult.length() - 1; i < 7; i++) {
			addResult += "0";
		}
		
		// Return the result of the addition
		return addResult;
	}
	
	/**
	 * Multiply mantissas from two numbers in IEEE 754 standard representation 
	 * @param ie3Num1 first number in IEEE 754 
	 * @param ie3Num2 second number in IEEE 754 
	 * @return
	 */
	private static String multiplyMantissas(String ie3Num1, String ie3Num2) {
		String tempResult = "";
		String result = "0";
		String mant1 = "";
		String mant2 = "";
		int One = 0;
		
		/* Get the mantissas */
		// Find the last one of the mantissas in order to skip all the zeros that are at the end of the number, if any
		for (int i = 14; i < ie3Num1.length(); i++) {
			if (ie3Num1.charAt(i) == '1') {
				One =i;
			}
			if (ie3Num2.charAt(i) == '1') { 
				One =i;
			}
		}
		// Get the mantissas obviating the implicit bit
		for (int i = 13 ;i <= 13 + One; i++){
			mant1 += ie3Num1.charAt(i);
			mant2 += ie3Num2.charAt(i);
		}
		
		/* Multiplication */
		// Start the loop from the end of the second number
		for (int i = mant2.length() - 1; i >= 0; i--) {
			// The digit is 0
			if (mant2.charAt(i) == '1') {
				// Copy the number
				for (int j = 0; j < mant1.length(); j++) {
					tempResult += mant1.charAt(j);
				}
				// Add extra 0s for the position of the iterator
				for (int j = mant2.length() - 1; j > i; j--) {
					tempResult += "0";
				}
				// Add that to the final result
				result = addNumbers(tempResult, result);
				// Reset the temporal variable
				tempResult = "";
			}
		}
		
		// Add the significant bits
		result = "10." + result;
		
		// Return the result
		return result;
	}

	/**
	 * Remove the implicit bit from the mantissa
	 * @param mantissa the mantissa from which to remove the implicit bit
	 * @return Mantissa without the implicit bit
	 */
	private static String removeImplicitBit(String mantissa) {
		String result = "";
		// Start counting from the position right after the decimal separator
		for (int i = 2; i < mantissa.length(); i++) {
			result += mantissa.charAt(i);
		}
		return result;
	}
	
	/* END MULTIPLICATION ---> */
	
	/**
	 * Multiply two float numbers
	 * @param num1 the first number to multiply
	 * @param num2 the second number to multiply
	 * @return IEEE 754 standard representation of the multiplication
	 */
	public static String floatMultiplier(float num1, float num2) {
		/* Transform the numbers into IEEE 754 */
		String ie3Num1 = floatToIEEE754(num1);
		String ie3Num2 = floatToIEEE754(num2);
		
		/* Get the sign of the result */
		String signResult = getSign(ie3Num1, ie3Num2);
		
		/* Add the exponents */
		String expResult = addExponents(ie3Num1,ie3Num2);
		
		/* Multiply mantissas */
		String mantResult = multiplyMantissas(ie3Num1, ie3Num2);
		
		/* Substract the bias */
		expResult = substractBias(expResult);
		
		/* Normalize the mantissa */
		mantResult = normalize(mantResult);
		
		/* Remove the implicit bit */
		mantResult = removeImplicitBit(mantResult);
		
		/* Write the result */
		String multiplicationResult = signResult + " " + expResult + " " + mantResult;
		return multiplicationResult;
	}
	
	public static void main(String[] args) {
		System.out.println("IEEE 754 multiplication of 7.5 and 1.5:");
		System.out.println(floatMultiplier((float)7.5, (float)1.5));

	}
}
